/**
 * 基础socket类
 * 提供WebSocket连接管理、数据发送接收和解析等功能
 * @date 2024.9.28
 * @author wz
 */
import { _decorator, director, macro, Scheduler } from 'cc';
import { MsgParser } from './MsgParser';
import { SocketReason, SocketMsgCallback, SocketReqWrap, SocketMsgData, SocketRespWrap, SocketState } from './BaseTypes';

export abstract class SocketBase {

    /**
     * socket连接自增id，用于生成唯一标识
     */
    private static SocketId: number = 0;
    /**
     * 当前socket实例的唯一id
     */
    private readonly id: number;
    /**
     * 连接的服务器地址
     */
    private _ip: string = '';
    /**
     * 连接的服务器端口
     */
    private _port: string = '';

    /**
     * 连接超时时间（单位：秒）
     */
    private _outTime: number = 0;
    /**
     * 连接状态（CLOSED, CONNECTING, OPEN等）
     */
    private _state: SocketState = SocketState.CLOSED;


    /**
     * 获取当前连接状态
     */
    get connectState(): SocketState {
        return this._state;
    }
    /**
     * 连接超时定时器句柄
     */
    private _connectTimeOutHandle: number = null;

    /**
     * 消息解析器实例
     */
    private _parser: MsgParser = null;

    /**
     * Cocos调度器实例，用于定期更新
     */
    private _schedule: Scheduler = null;

    /**
     * WebSocket实例
     */
    private _socket: WebSocket = null;

    /**
     * 待发送数据队列
     */
    private _sendDataCache: Uint8Array[] = [];

    /**
     * 构造函数，初始化socket id
     */
    constructor() {
        this.id = SocketBase.SocketId++;
    }

    /**
     * 初始化连接参数
     * @param ip 服务器地址
     * @param port 服务器端口
     * @param outTime 连接超时时间（秒），默认5秒
     */
    private _init(ip: string, port: string, outTime: number = 5) {
        this._ip = ip;
        this._port = port;
        this._outTime = outTime;
        this._state = SocketState.CLOSED;
        this._parser = new MsgParser();
        this._schedule = director.getScheduler();
    }


    /**
     * 连接服务器
     * 创建WebSocket实例并设置各种事件回调
     */
    private _onconnect() {
        // 如果不是关闭状态则不进行连接
        if (this._state != SocketState.CLOSED) return;

        let url = "ws://" + this._ip + ":" + this._port;

        this._socket = new WebSocket(url);
        this._socket.binaryType = "arraybuffer"; // 设置接收二进制数据类型

        this._state = SocketState.CONNECTING

        this._startConnectTimeOut() // 启动连接超时检测

        // 连接成功事件处理
        this._socket.onopen = (event: Event) => {
            console.log(`ip:${this._ip},port:${this._port}---onopen`)
            // 清理超时定时器
            this._clearConnectTimeOut()

            this._state = SocketState.OPEN
            // 启动定期更新
            this._schedule.schedule(this._onupdate.bind(this), { id: this.id.toString() }, 0, macro.REPEAT_FOREVER, 0, false);

            // 触发连接成功回调
            this.onConnectSuccess();
        };
        // 消息接收事件处理
        this._socket.onmessage = (msg: MessageEvent) => {
            this._onmessage(msg);
        };
        // 错误事件处理
        this._socket.onerror = (event: Event) => {
            console.log(`id:${this.id} ip:${this._ip},port:${this._port}---onerror`)
            this._onclose(SocketReason.CLOSE);
        };
        // 关闭事件处理
        this._socket.onclose = (event: CloseEvent) => {
            console.log(`id:${this.id} ip:${this._ip},port:${this._port}---onclose`)
            this._onclose(SocketReason.CLOSE);
        };
    }

    /**
     * 启动连接超时检测定时器
     */
    private _startConnectTimeOut() {
        // 超时回调
        this._connectTimeOutHandle = setTimeout(() => {
            console.log(`ip:${this._ip},port:${this._port}---ontimeout`)
            this._onclose(SocketReason.TIMEOUT);
        }, this._outTime * 1000);
    }

    /**
     * 清除连接超时定时器
     */
    private _clearConnectTimeOut() {
        if (this._connectTimeOutHandle) {
            clearTimeout(this._connectTimeOutHandle);
            this._connectTimeOutHandle = null;
        }
    }

    /**
     * 定期更新处理，主要用于发送队列中的数据
     */
    private _onupdate() {
        // 刷新发送缓存队列
        this._flushSendData();
    }

    /**
     * 发送队列中的数据
     * 每次从队列头取出一条数据发送
     */
    private _flushSendData() {
        if (this._state == SocketState.OPEN && this._sendDataCache.length > 0) {
            if (this._socket) {
                let data = this._sendDataCache.shift();
                this._socket.send(data.buffer);
            }
        }
    }

    /**
     * 处理收到的消息
     * @param event WebSocket消息事件
     */
    private _onmessage(event: MessageEvent) {
        if (event.data instanceof ArrayBuffer) {
            const binary = event.data;
            this._parser.parse(binary, (msgType: number, data: Uint8Array) => {
                // 转换接收到的数据格式
                const respWrap = this.convertRecvData({ msgType, data });
                // 触发消息接收回调
                this.onMessege(msgType, respWrap);
            })
        }
        else {
            console.error("接收数据类型错误")
        }
    }

    /**
     * 处理连接关闭
     * @param reason 关闭原因
     */
    protected _onclose(reason: SocketReason) {
        if (this._state == null || this._state == SocketState.CLOSED) return;

        this._state = SocketState.CLOSED

        // 清理发送缓存队列
        this._sendDataCache.length = 0;

        // 清理连接超时定时器
        this._clearConnectTimeOut()

        // 清理解析器
        if (this._parser)
            this._parser.clear()

        // 停止定期更新
        if (this._schedule)
            this._schedule.unschedule(this._onupdate.bind(this), { id: this.id.toString() })

        // 清理WebSocket实例
        if (this._socket) {
            this._socket.onopen = null;
            this._socket.onmessage = null;
            this._socket.onerror = null;
            this._socket.onclose = null;
            this._socket.close();
            this._socket = null;
        }
        // 触发连接关闭回调
        this.onConnectClose(reason);
    }


    /**
     * 发送数据
     * @param cmd 命令ID
     * @param body 消息体数据，默认为空对象
     * @param callback 发送回调
     */
    sendData(cmd: number, body: any = {}, callback?: SocketMsgCallback) {
        // 转换待发送的数据格式
        const msgData = this.convertSendData({ cmd, body }, callback);
        // 打包数据
        const sendData = this._parser.wrap(msgData.msgType, msgData.data);
        if (sendData) {
            // 加入发送队列
            this._sendDataCache.push(sendData);
            // 尝试立即发送
            this._flushSendData();
        }
    }

    /**
     * 连接服务器
     * @param ip 服务器地址
     * @param port 服务器端口
     * @param outTime 连接超时时间（秒），默认5秒
     */
    connect(ip: string, port: string, outTime: number = 5) {
        // 先关闭可能存在的连接
        this.forceClose();
        // 初始化连接参数
        this._init(ip, port, outTime);
        // 开始连接
        this._onconnect();
    }

    /**
     * 主动关闭连接
     */
    forceClose() {
        this._onclose(SocketReason.FORCE);
    }


    /**
     * 转换发送数据格式（子类需实现）
     * @param data 请求包装数据
     * @param callback 发送回调函数
     * @returns 转换后的消息数据
     */
    abstract convertSendData(data: SocketReqWrap, callback?: SocketMsgCallback): SocketMsgData;

    /**
     * 转换接收数据格式（子类需实现）
     * @param data 收到的消息数据
     * @returns 转换后的响应包装数据
     */
    abstract convertRecvData(data: SocketMsgData): SocketRespWrap;

    /**
     * 连接成功回调（子类需实现）
     */
    abstract onConnectSuccess();

    /**
     * 权限验证成功回调（子类需实现）
     */
    abstract onAuthSuccess();

    /**
     * 连接关闭回调（子类需实现）
     * @param reason 关闭原因
     */
    abstract onConnectClose(reason: SocketReason);

    /**
     * 消息接收回调（子类需实现）
     * @param data 接收到的响应数据
     */
    abstract onMessege(msgType: number, data: SocketRespWrap);
}